//Download by http://www.NewXing.com
/*
 *   Copyright (C) =USTC= Fu Li
 *
 *   Author   :  Fu Li
 *   Create   :  2005-9-25
 *   Home     :  www.crazy-bit.com
 *   Mail     :  crazybit@263.net
 *   History  :  
 */
#pragma once
#pragma warning (disable : 4786) // identifier was truncated to '255' characters in the browser information
#include <comdef.h>
#include <FSTREAM>
#include <DEQUE>
#include <ASSERT.H>
#include <afxinet.h>
#include <strsafe.h>

#pragma comment(lib, "wininet.lib")

//=============================================================================
/**
 *  Map table.
 */
template<class T1, class T2>
class PCL_TT_Convertor
{
public:
    /// T1 ==> T2
    T2 First_to_Second (const T1& t1, const T2& t2Default) const
    {
        int   i = GetIndexT1(t1) ;
        return (i==-1) ? t2Default : m_t2Tab[i] ;
    }

    /// T2 ==> T1
    T1 Second_to_First (const T2& t2, const T1& t1Default) const
    {
        int   i = GetIndexT2(t2) ;
        return (i==-1) ? t1Default : m_t1Tab[i] ;
    }

    /// Get element count.
    int GetElementCount() const
    {
        if (m_t1Tab.size() == m_t2Tab.size())
            return (int)m_t1Tab.size();
        assert(false) ;
        return 0 ;
    }

    /// Get 0-based index in array by t1, return -1 if not found.
    int GetIndexT1 (const T1& t1) const
    {
        for (size_t i=0 ; i < m_t1Tab.size() ; i++)
        {
            if (m_t1Tab[i] == t1)
                return (int)i ;
        }
        return -1 ;
    }
    /// Get 0-based index in array by t2, return -1 if not found.
    int GetIndexT2 (const T2& t2) const
    {
        for (size_t i=0 ; i < m_t2Tab.size() ; i++)
        {
            if (m_t2Tab[i] == t2)
                return (int)i ;
        }
        return -1 ;
    }

    /// Get first element.
    const T1& GetT1 (int n) const {assert(IsValidIndex(n)); return m_t1Tab[n];}
          T1& GetT1 (int n)       {assert(IsValidIndex(n)); return m_t1Tab[n];}
    /// Get second element.
    const T2& GetT2 (int n) const {assert(IsValidIndex(n)); return m_t2Tab[n];}
          T2& GetT2 (int n)       {assert(IsValidIndex(n)); return m_t2Tab[n];}

    /// Add a new element into table.
    void AddElement (const T1& t1, const T2& t2)
    {
        m_t1Tab.push_back (t1) ;
        m_t2Tab.push_back (t2) ;
    }

    /// Remove an element from table by 0-based index.
    void RemoveElement (int nIndex)
    {
        if (IsValidIndex(nIndex))
        {
            m_t1Tab.erase(m_t1Tab.begin() + nIndex) ;
            m_t2Tab.erase(m_t2Tab.begin() + nIndex) ;
        }
        else
            {assert(false);}
    }
    /// Remove an element from table.
    void RemoveElement (const T1& t1, const T2& t2)
    {
        for (int i=0 ; i < GetElementCount() ; i++)
            if ((m_t1Tab[i] == t1) && (m_t2Tab[i] == t2))
            {
                RemoveElement(i) ;
                return ; // must return immediately
            }
    }

    /// Clear all elements.
    void Clear()
    {
        m_t1Tab.clear() ;
        m_t2Tab.clear() ;
    }

    /// Pop a element from front of list.
    void PopFront()
    {
        if (GetElementCount())
        {
            m_t1Tab.pop_front() ;
            m_t2Tab.pop_front() ;
        }
    }

private:
    bool IsValidIndex (int nIndex) const
    {
        return (nIndex >= 0) && (nIndex < GetElementCount()) ;
    }

private:
    std::deque<T1>   m_t1Tab ;
    std::deque<T2>   m_t2Tab ;
};

//=============================================================================
/**
 *  Create a thread to download a file.
 *  The owner window will receive notify message.
 */
template<class T>
class FCDownloadFileWndBase : public T
{
public:
    /// Default constructor.
    FCDownloadFileWndBase () {}
    /// Dialog constructor.
    FCDownloadFileWndBase (UINT nID, CWnd* pParent) : T(nID, pParent) {}
    /// CFormView constructor.
    FCDownloadFileWndBase (UINT nID) : T(nID) {}

    virtual ~FCDownloadFileWndBase()
    {
        // wait for all thread exit.
        for (int i=0 ; i < m_ThreadTab.GetElementCount() ; i++)
        {
            if (::WaitForSingleObject (m_ThreadTab.GetT1(i), 10 * 1000) == WAIT_TIMEOUT)
            {
                ::TerminateThread (m_ThreadTab.GetT1(i), 0) ;
            }
            ::CloseHandle (m_ThreadTab.GetT1(i)) ;
        }
    }

    /// Create a thread to download file, return immediately if the file is downloading.
    void DownloadFile (LPCTSTR strFileURL, int nPriority=THREAD_PRIORITY_IDLE)
    {
        if (!lstrlen(strFileURL))
            return ;

        HANDLE   h = m_ThreadTab.Second_to_First(strFileURL, NULL) ;
        if (h)
        {
            if (::WaitForSingleObject(h,0) == WAIT_TIMEOUT) // downloading now
            {
                // ASSERT(FALSE);
                return ;
            }
            else
            {
                m_ThreadTab.RemoveElement (h, strFileURL) ; // remove
                ::CloseHandle (h) ;
            }
        }

        DWORD      _i ;
        PARA_DATA  * pPara = new PARA_DATA ;
        pPara->pThis = this ;
        pPara->strFileURL = strFileURL ;
        h = ::CreateThread (NULL, 0, __uis_DownloadFile, pPara, CREATE_SUSPENDED, &_i); ASSERT(h);
        m_ThreadTab.AddElement (h, strFileURL) ;
        ::SetThreadPriority (h, nPriority) ;
        if(::ResumeThread (h) == -1)
		{
			AfxMessageBox(_T("Thread error!"));
		}
    }

protected:
    /**
     *  @name Owner window will receive these notify.
     */
    //@{
    /**
     *  Check last modified time of download file URL.
     *  @return continue to download file return TRUE, otherwise return FALSE.
     */
    virtual BOOL DownloadFile_OnCheckTime (CString strFileURL, CString strTime) =0 ;
    /// The file download finished.
    virtual void DownloadFile_OnFinished (CString strFileURL, char* pBuffer, int nLength) {}
    /// Fill proxy's username & password to validate.
    virtual void DownloadFile_OnProxyValidate (CString strFileURL, CString& strUsername, CString& strPassword) {}
    /// Error during download.
    virtual void DownloadFile_OnError (CString strFileURL) {}
    /// Start download file.
    virtual void DownloadFile_OnStartDownload (CString strFileURL) {}
    /**
     *  Download progress.
     *  @param nNow : downloaded bytes.
     *  @param nTotal : total bytes, don't know size if 0.
     */
    virtual void DownloadFile_OnProgress (CString strFileURL, int nNow, int nTotal) {}
    //@}

    // inner
    virtual LRESULT WindowProc (UINT msg, WPARAM wParam, LPARAM lParam)
    {
        if (msg == GetMsg_CheckTime())
        {
            WM_DLF_CHECKTIME   * p = (WM_DLF_CHECKTIME*)wParam ;
            return DownloadFile_OnCheckTime (*p->pstrFileURL, *p->pStrTime) ;
        }
        else if (msg == GetMsg_DownFinished())
        {
            WM_DLF_FINISHED   * p = (WM_DLF_FINISHED*)wParam ;
            DownloadFile_OnFinished (*p->pstrFileURL, p->pBuffer, p->nLength) ;
            return 0 ;
        }
        else if (msg == GetMsg_ProxyValidate())
        {
            WM_DLF_PROXY_VALIDATE   * p = (WM_DLF_PROXY_VALIDATE*)wParam ;
            DownloadFile_OnProxyValidate (*p->pstrFileURL, *p->pStrUsername, *p->pStrPassword) ;
            return 0 ;
        }
        else if (msg == GetMsg_Progress())
        {
            WM_DLF_PROGRESS   * p = (WM_DLF_PROGRESS*)wParam ;
            DownloadFile_OnProgress (*p->pstrFileURL, p->nNow, p->nTotal) ;
            return 0 ;
        }
        else if (msg == GetMsg_Error())
        {
            DownloadFile_OnError (*(CString*)wParam) ;
            return 0 ;
        }
        else if (msg == GetMsg_StartDownload())
        {
            DownloadFile_OnStartDownload (*(CString*)wParam) ;
            return 0 ;
        }
        return T::WindowProc(msg, wParam, lParam) ;
    }

private:
    // thread handle <==> download file URL
    PCL_TT_Convertor<HANDLE, CString>   m_ThreadTab ;

    // information pass to thread
    struct PARA_DATA
    {
        FCDownloadFileWndBase   * pThis ;
        CString   strFileURL ;
    };

    // message notify owner window
    static UINT GetMsg_CheckTime()     {return ::RegisterWindowMessage (_T("UISTONE_WM_DOWNFILE_CHECKTIME")); }
    static UINT GetMsg_DownFinished()  {return ::RegisterWindowMessage (_T("UISTONE_WM_DOWNFILE_DOWNFINISHED"));}
    static UINT GetMsg_ProxyValidate() {return ::RegisterWindowMessage (_T("UISTONE_WM_DOWNFILE_PROXYVALIDATE"));}
    static UINT GetMsg_Error()         {return ::RegisterWindowMessage (_T("UISTONE_WM_DOWNFILE_ERROR"));}
    static UINT GetMsg_StartDownload() {return ::RegisterWindowMessage (_T("UISTONE_WM_DOWNFILE_STARTDOWNLOAD"));}
    static UINT GetMsg_Progress()      {return ::RegisterWindowMessage (_T("UISTONE_WM_DOWNFILE_PROGRESS"));}

    // inner notify message struct
    struct WM_DLF_CHECKTIME
    {
        WM_DLF_CHECKTIME (CString* pURL, CString* pT) : pstrFileURL(pURL), pStrTime(pT) {}
        CString   * pstrFileURL ;
        CString   * pStrTime ;
    };
    struct WM_DLF_FINISHED
    {
        WM_DLF_FINISHED (CString* pURL, char* pB, int n) : pstrFileURL(pURL), pBuffer(pB), nLength(n) {}
        CString   * pstrFileURL ;
        char      * pBuffer ;
        int       nLength ;
    };
    struct WM_DLF_PROXY_VALIDATE
    {
        WM_DLF_PROXY_VALIDATE (CString* pURL, CString* pN, CString* pP) : pstrFileURL(pURL), pStrUsername(pN), pStrPassword(pP) {}
        CString   * pstrFileURL ;
        CString   * pStrUsername ;
        CString   * pStrPassword ;
    };
    struct WM_DLF_PROGRESS
    {
        WM_DLF_PROGRESS (CString* pURL, int nN, int nT) : pstrFileURL(pURL), nNow(nN), nTotal(nT) {}
        CString   * pstrFileURL ;
        int       nNow ;
        int       nTotal ;
    };

    LRESULT df_Notify (UINT msg, void* pNotify)
    {
        if (!::IsWindow (GetSafeHwnd()))
            return FALSE ;
        return SendMessage (msg, (WPARAM)pNotify) ;
    }

    struct __FCAutoInitializeCOM
    {
        __FCAutoInitializeCOM()  {CoInitialize(0);}
        ~__FCAutoInitializeCOM() {CoUninitialize();}
    };

    static bstr_t QueryTempFilePath()
    {
        TCHAR   szTmpPath[MAX_PATH],
                szFilename[MAX_PATH] ;
        ::GetTempPath (MAX_PATH, szTmpPath) ;
        ::GetTempFileName (szTmpPath, TEXT("foo"), 0, szFilename) ;
        return szFilename ;
    }

    static void LoadFileToBuffer (const char* szFilename, char*& pBuffer, int& nLength)
    {
        pBuffer=0; nLength=0;
        std::ifstream     inFile (szFilename, std::ios::in|std::ios::binary) ;
        if (!inFile.is_open())
            {assert(false); return;}

        // get file length
        inFile.seekg (0, std::ios::end) ;
        nLength = (int)inFile.tellg() ;
        if (nLength <= 0)
            {assert(false); return;}

        // read file into memory
        pBuffer = new char[nLength + 8] ;
        memset (&pBuffer[nLength], 0, 8) ;
        inFile.seekg (0, std::ios::beg) ;
        inFile.read (pBuffer, nLength) ;
    }


	static void ErrorExit(LPTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message and exit the process

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); 
	::StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
	::MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    ExitProcess(dw); 
}


    // encapsuled download thread.
    static DWORD WINAPI __uis_DownloadFile (LPVOID lpParameter)
    {
        __FCAutoInitializeCOM      _aAutoCOM ;
        std::auto_ptr<PARA_DATA>   pPara ((PARA_DATA*)lpParameter) ;

        // start download file
        char   * pBuf = NULL ;
        int    nBufLen = 0 ;
        TRY
        {
            // connection
            CInternetSession   sess ;
            sess.SetOption (INTERNET_OPTION_CONNECT_TIMEOUT, 300 * 1000) ;
            sess.SetOption (INTERNET_OPTION_CONNECT_BACKOFF, 1000) ;
            sess.SetOption (INTERNET_OPTION_CONNECT_RETRIES, 5) ;

            DWORD       dwFlag = INTERNET_FLAG_TRANSFER_BINARY|INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_RELOAD ;
            CHttpFile   * pF = (CHttpFile*)sess.OpenURL(pPara->strFileURL, 1, dwFlag); ASSERT(pF);
            if (!pF)
            {
					AfxMessageBox(_T("OpenURL error!"));
					AfxThrowInternetException(1);
			}

            // connection status
            CString      str ;
            pF->QueryInfo (HTTP_QUERY_STATUS_CODE, str) ;

            // Proxy Authentication Required
            if (str == _T("407"))
            {
                CString   strUsername, strPassword ;
                pPara->pThis->df_Notify (GetMsg_ProxyValidate(), &WM_DLF_PROXY_VALIDATE(&pPara->strFileURL, &strUsername, &strPassword)) ;
                pF->SetOption (INTERNET_OPTION_PROXY_USERNAME, (VOID*)(LPCTSTR)strUsername, strUsername.GetLength()) ;
                pF->SetOption (INTERNET_OPTION_PROXY_PASSWORD, (VOID*)(LPCTSTR)strPassword, strPassword.GetLength()) ;
                pF->SendRequest (NULL) ;
            }

            pF->QueryInfo (HTTP_QUERY_STATUS_CODE, str) ;
            if (str != _T("200"))
            {
                pF->Close() ;
                delete pF ;
				AfxMessageBox(_T("QueryInfo error!"));
                AfxThrowInternetException(1);
            }

            // confirm update
            pF->QueryInfo (HTTP_QUERY_LAST_MODIFIED, str) ;
            if (!pPara->pThis->df_Notify (GetMsg_CheckTime(), &WM_DLF_CHECKTIME(&pPara->strFileURL, &str)))
            {
                pF->Close() ;
                delete pF ;
				AfxMessageBox(_T("df_Notify error!"));
                AfxThrowInternetException(1);
            }

            // start download
            pF->QueryInfo (HTTP_QUERY_CONTENT_LENGTH, str) ; // file's length
            pPara->pThis->df_Notify (GetMsg_StartDownload(), &pPara->strFileURL) ;

            if (_ttoi(str))
            {
                // know file's size
                int     nLen = (nBufLen = _ttoi(str)) ;
                char    * p = (pBuf = new char[nLen+8]) ;
                ZeroMemory (p, nLen+8) ;

                while (IsWindow(pPara->pThis->GetSafeHwnd()))
                {
                    // download 8K every
                    int   n = pF->Read (p, (nLen < 8192) ? nLen : 8192) ;
                    if (n <= 0)
                        break ; // success exit
                    p += n ; nLen -= n ;

                    pPara->pThis->df_Notify (GetMsg_Progress(), &WM_DLF_PROGRESS(&pPara->strFileURL, nBufLen-nLen, nBufLen)) ;
                }

                // interrupted
                if (nLen != 0)
                {
                    delete[] pBuf; pBuf=NULL;
                    nBufLen = 0 ;
                }
            }
            else
            {
                // don't know file's size, save context to a temp file.
                bstr_t   strFile = QueryTempFilePath() ;
                CFile    outFile (strFile, CFile::modeCreate|CFile::modeReadWrite) ;
                int      n = (int)pF->GetLength() ;
                while (IsWindow(pPara->pThis->GetSafeHwnd()) && n)
                {
                    char   * pa = new char[n] ;
                    n = pF->Read (pa, n) ;
                    outFile.Write (pa, n) ;
                    pPara->pThis->df_Notify (GetMsg_Progress(), &WM_DLF_PROGRESS(&pPara->strFileURL, (int)outFile.GetLength(), 0)) ;
                    n = (int)pF->GetLength() ;
                    delete[] pa ;
                }
                outFile.Close() ;

                // success
                if (n == 0)
                {
                    DWORD   dw ;
                    if (::InternetQueryDataAvailable ((HINTERNET)(*pF), &dw, 0, 0) && (dw == 0))
                    {
                        LoadFileToBuffer (strFile, pBuf, nBufLen) ;
                    }
                }
                ::DeleteFile(strFile) ;
            }

            pF->Close() ;
            delete pF ;
        }
        CATCH_ALL(e) 
		{
			//AfxMessageBox(_T("__uis_DownloadFile Thread error!"));
			ErrorExit(_T("__uis_DownloadFile"));
		}
        END_CATCH_ALL

        if (pBuf)
        {
            pPara->pThis->df_Notify (GetMsg_DownFinished(), &WM_DLF_FINISHED(&pPara->strFileURL, pBuf, nBufLen)) ;
            delete[] pBuf ;
        }
        else
        {
            pPara->pThis->df_Notify (GetMsg_Error(), &pPara->strFileURL) ;
        }
        return 0 ;
    }
};
